use super::ffi::{
    osMutexAcquire, osMutexDelete, osMutexId_t, osMutexNew, osMutexRelease, osWaitForever,
};
use crate::Error;
use core::cell::UnsafeCell;
use core::ops::{Deref, DerefMut};
use core::ptr::null_mut;

pub type LockResult<Guard> = Result<Guard, Error>;

pub struct Mutex<T: ?Sized> {
    mutex_id: osMutexId_t,
    data: UnsafeCell<T>,
}

unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}

impl<T> Mutex<T> {
    pub fn new(data: T) -> Self {
        Self {
            mutex_id: unsafe { osMutexNew(null_mut()) },
            data: UnsafeCell::new(data),
        }
    }
}

impl<T: ?Sized> Mutex<T> {
    pub fn lock(&self) -> LockResult<MutexGuard<'_, T>> {
        self.lock_internal();
        unsafe { MutexGuard::new(self) }
    }

    fn lock_internal(&self) {
        unsafe {
            // infoln!("{:p} lock", self.mutex_id);
            // printf(cstr!("lock\n\0"));
            osMutexAcquire(self.mutex_id, osWaitForever);
        };
    }

    pub fn unlock(guard: MutexGuard<'_, T>) {
        drop(guard);
    }

    fn unlock_internal(&self) {
        unsafe {
            // infoln!("{:p} unlock internal", self.mutex_id);
            osMutexRelease(self.mutex_id)
        };
    }
}

impl<T: ?Sized> Drop for Mutex<T> {
    fn drop(&mut self) {
        if !self.mutex_id.is_null() {
            unsafe {
                // infoln!("drop mutex {:p}", self.mutex_id);
                osMutexDelete(self.mutex_id);
            }
        }
    }
}

pub struct MutexGuard<'a, T: ?Sized + 'a> {
    lock: &'a Mutex<T>,
}

impl<T: ?Sized> !Send for MutexGuard<'_, T> {}
unsafe impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T> {}

impl<'mutex, T: ?Sized> MutexGuard<'mutex, T> {
    unsafe fn new(lock: &'mutex Mutex<T>) -> LockResult<MutexGuard<'mutex, T>> {
        Ok(Self { lock })
    }
}

impl<T: ?Sized> Deref for MutexGuard<'_, T> {
    type Target = T;

    fn deref(&self) -> &T {
        unsafe { &*self.lock.data.get() }
    }
}

impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
    fn deref_mut(&mut self) -> &mut T {
        unsafe { &mut *self.lock.data.get() }
    }
}

impl<T: ?Sized> Drop for MutexGuard<'_, T> {
    #[inline]
    fn drop(&mut self) {
        // infoln!("drop mutex gard");
        self.lock.unlock_internal();
    }
}
